package com.gd.iot.energy.service.datapush.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.gd.iot.energy.code.ResponseCode;
import com.gd.iot.energy.consts.SystemConsts;
import com.gd.iot.energy.exception.MyException;
import com.gd.iot.energy.po.datamanage.EquipInstructionValuePOWithBLOBs;
import com.gd.iot.energy.po.datamanage.ReportNbPrimaryDataPO;
import com.gd.iot.energy.po.datamanage.ReportNbWaterDataPO;
import com.gd.iot.energy.po.systemadmin.AreaArchivesPO;
import com.gd.iot.energy.pojo.datapush.ctwing.dto.DataChangeDTO;
import com.gd.iot.energy.pojo.datapush.ctwing.dto.DeviceEventDTO;
import com.gd.iot.energy.pojo.datapush.ctwing.dto.DeviceOnOfflineDTO;
import com.gd.iot.energy.pojo.datapush.ctwing.dto.DeviceResponseDTO;
import com.gd.iot.energy.service.datamanage.EquipInstructionValueService;
import com.gd.iot.energy.service.datamanage.ReportNbPrimaryDataService;
import com.gd.iot.energy.service.datamanage.ReportNbWaterDataService;
import com.gd.iot.energy.service.datapush.CTWINGSubscribeService;
import com.gd.iot.energy.service.systemadmin.AreaArchivesService;
import com.gd.iot.energy.util.DecodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;

/**
 * 天翼推送
 *
 * @author kris
 * @date Created at 8:25 上午 2020/8/29
 */
@Service
@Slf4j
public class CTWINGSubscribeServiceImpl implements CTWINGSubscribeService {

    private static final String START_STR = "fe";
    private static final String START_STR2 = "68";

    @Autowired
    private EquipInstructionValueService equipInstructionValueService;

    @Autowired
    private AreaArchivesService areaArchivesService;

    @Autowired
    private ReportNbPrimaryDataService reportNbPrimaryDataService;

    @Autowired
    private ReportNbWaterDataService reportNbWaterDataService;

    @Override
    public void dataChange(DataChangeDTO dataChangeDTO) {
        log.info(JSONUtil.toJsonStr(dataChangeDTO));
        String deviceId = dataChangeDTO.getDeviceId();
        String imei = dataChangeDTO.getIMEI();
        AreaArchivesPO areaArchivesPO = areaArchivesService.getByImei(imei);
        saveInstruction(areaArchivesPO, areaArchivesPO, imei, deviceId, SystemConsts.InstructionTypeEnum.DATA_CHANGE.value);

        DataChangeDTO.PayloadDecode payloadDecode = decodeDataChangePayLoad(dataChangeDTO.getPayload());

        saveWaterData(payloadDecode,dataChangeDTO,areaArchivesPO);
    }

    /**
     * 保存水表数据
     * @param payloadDecode
     * @param dataChangeDTO
     * @param areaArchivesPO
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveWaterData(DataChangeDTO.PayloadDecode payloadDecode, DataChangeDTO dataChangeDTO, AreaArchivesPO areaArchivesPO) {
        ReportNbPrimaryDataPO reportNbPrimaryDataPO = new ReportNbPrimaryDataPO();
        if(areaArchivesPO!=null) {
            reportNbPrimaryDataPO.setArchivesAddress(areaArchivesPO.getCustomerAddress());
            reportNbPrimaryDataPO.setArchivesName(areaArchivesPO.getCustomerName());
            reportNbPrimaryDataPO.setArchivesNum(areaArchivesPO.getCustomerSerialnumber());
            reportNbPrimaryDataPO.setAreaName(areaArchivesPO.getOreaName());
            reportNbPrimaryDataPO.setAreaOrgId(areaArchivesPO.getMeasureAreaOrg());
        }
        reportNbPrimaryDataPO.setCreateDate(new Date());
        reportNbPrimaryDataPO.setEnergyType(SystemConsts.EnergyTypeEnum.WATER.value);
        reportNbPrimaryDataPO.setImei(dataChangeDTO.getIMEI());
        reportNbPrimaryDataPO.setCumulantFlow(payloadDecode.getTotal().toPlainString());
        reportNbPrimaryDataPO.setInstantFlow(payloadDecode.getInstant().toPlainString());
        reportNbPrimaryDataPO.setInverseCumulantFlow("0");
        reportNbPrimaryDataPO.setPositiveCumulantFlow(payloadDecode.getTotal().toPlainString());
        reportNbPrimaryDataPO.setIsDel(SystemConsts.BooleanEnum.FALSE.value);
        reportNbPrimaryDataPO.setMemo("NB-实时流量");
        reportNbPrimaryDataPO.setReadDate(DateUtil.formatDateTime(new Date()));
        reportNbPrimaryDataPO.setReadTime(DateUtil.formatDateTime(new Date()));
        reportNbPrimaryDataPO.setModifyDate(new Date());
        reportNbPrimaryDataService.save(reportNbPrimaryDataPO);

        ReportNbWaterDataPO reportNbWaterDataPO = new ReportNbWaterDataPO();
        reportNbWaterDataPO.setCreateDate(new Date());
        reportNbWaterDataPO.setDataId(reportNbPrimaryDataPO.getId());
        reportNbWaterDataPO.setEnergyType(SystemConsts.EnergyTypeEnum.WATER.value);
        reportNbWaterDataPO.setIsDel(SystemConsts.BooleanEnum.FALSE.value);
        reportNbWaterDataPO.setModifyDate(new Date());
        reportNbWaterDataService.save(reportNbWaterDataPO);
    }

    /**
     * base64 16进制报文
     * <p>
     * 主动上报报文：
     * FE FE FE 68 10
     * 78 56 34 12 00
     * 00 00 81 16 90
     * 1F 01 2C 25 00
     * 00 00 35 50 68
     * 00 00 00 30 06
     * 15 03 18 20 00
     * 00 97 16 36 34
     * 26
     *
     * 68 为帧起始符 68H
     * 10 为仪表类型 T
     * 78 为地址A0
     * 56 为地址A1
     * 34 为地址A2
     * 12 为地址A3
     * 00 为地址A4
     * 00 为地址A5
     * 00 为地址A6（A0、A1、A2、A3为要读取的水表的地址，从低位到高位）
     * 81 为控制码 C
     * 16 为数据长度域 L（90 1F 01 2C 25 00 00 00 35 50 68 00 00 00 30 06 15 03 18 20 00 00 共计16个字符）
     * 90 为数据标识DI0
     * 1F  为数据标识DI1
     * 01 为序列号SER
     * 2C 25 00 00 00 为累计流量 0.025M3，2C 为累计流量单位代号表示M3（表1）
     * 35 50 68 00 00 为流速量 0.6850M3/h，35 为流速单位代号表示M3/h（表1）
     * 00 30 06 15 03 18 20 为实时时间2018年3月15日6时30分0秒
     * 00 00 为状态字（具体定义见表3.1、表3.2）
     * 97 为校验码 CS（68 10 78 56 34 12 00 00 00 81 16 90 1F 01 2C 25 00 00 00 35 50 68 00 00 00 30 06 15 03 18 20 00 00）进行二进制算术累加，不计超过FFH的溢出值）
     * 16 为结束符 16H
     * 36 34为电池电压3.634V
     * 26 为CSQ信号强度
     * <p/>
     */
    private DataChangeDTO.PayloadDecode decodeDataChangePayLoad(DataChangeDTO.Payload payload) {
        List<String> hexList = DecodeUtil.base64ToHexList(payload.getAPPdata());
        String s0 = hexList.get(0);
        String s1 = hexList.get(1);
        String s2 = hexList.get(2);
        String s3 = hexList.get(3);
        if (!START_STR.equals(s0) || !START_STR.equals(s1) || !START_STR.equals(s2) || !START_STR2.equals(s3)) {
            throw new MyException(ResponseCode.PARAMETER_ERROR);
        }
        if(!validateCrc(hexList)){
            throw new MyException(ResponseCode.PARAMETER_ERROR);
        }
        DataChangeDTO.PayloadDecode payloadDecode = new DataChangeDTO.PayloadDecode();
        payloadDecode.setDeviceType(hexList.get(4));
        payloadDecode.setDeviceAddress(hexList.get(8) + hexList.get(7) + hexList.get(6) + hexList.get(5));
        payloadDecode.setControlCode(hexList.get(12));
        payloadDecode.setLength(hexList.get(13));
        payloadDecode.setDi0(hexList.get(14));
        payloadDecode.setDi1(hexList.get(15));
        payloadDecode.setSerial(hexList.get(16));
        BigDecimal total = BigDecimal.valueOf(
                Integer.parseInt(hexList.get(21)) * 1000D
                        + Integer.parseInt(hexList.get(20)) * 10D
                        + Integer.parseInt(hexList.get(19)) / 10D
                        + Integer.parseInt(hexList.get(18)) / 1000D
        ).setScale(3, RoundingMode.HALF_UP);
        payloadDecode.setTotal(total);
        payloadDecode.setTotalUnit("M3");
        BigDecimal instant = BigDecimal.valueOf(
                Integer.parseInt(hexList.get(26)) * 100D
                        + Integer.parseInt(hexList.get(25)) * 1D
                        + Integer.parseInt(hexList.get(24)) / 100D
                        + Integer.parseInt(hexList.get(23)) / 10000D
        ).setScale(4, RoundingMode.HALF_UP);
        payloadDecode.setInstant(instant);
        payloadDecode.setInstantUnit("M3/h");
        String year = hexList.get(33)+hexList.get(32);
        String month = hexList.get(31);
        String day = hexList.get(30);
        String hour = hexList.get(29);
        String minute = hexList.get(28);
        String second = hexList.get(27);
        Date time = DateUtil.parseDateTime(year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second);
        payloadDecode.setTime(time);
        payloadDecode.setStatus(hexList.get(34)+hexList.get(35));
        payloadDecode.setCrc(hexList.get(36));
        BigDecimal voltage = BigDecimal.valueOf(Integer.parseInt(hexList.get(38))/10D+Integer.parseInt(hexList.get(39))/1000D);
        payloadDecode.setVoltage(voltage);
        payloadDecode.setSignal(Integer.parseInt(hexList.get(40)));
        return payloadDecode;
    }

    /**
     * crc 校验
     * @param hexList
     * @return
     */
    private boolean validateCrc(List<String> hexList) {
        int crc = 0;
        for (int i = 3; i < 36; i++) {
            byte b = DecodeUtil.hexToByte(hexList.get(i));
            crc += b;
        }
        return crc%256 != Integer.parseInt(hexList.get(27));
    }

    @Override
    public void deviceResponse(DeviceResponseDTO deviceResponseDTO) {
        log.info(JSONUtil.toJsonStr(deviceResponseDTO));
        String deviceId = deviceResponseDTO.getDeviceId();
        AreaArchivesPO areaArchivesPO = areaArchivesService.getByDeviceId(deviceId);

        saveInstruction(deviceResponseDTO, areaArchivesPO, null, deviceId, SystemConsts.InstructionTypeEnum.DEVICE_RESPONSE.value);

    }

    private <T> void saveInstruction(T t, AreaArchivesPO areaArchivesPO, String imei, String deviceId, Integer instructionType) {
        EquipInstructionValuePOWithBLOBs valuePOWithBLOBs = new EquipInstructionValuePOWithBLOBs();
        valuePOWithBLOBs.setReceiveCommand(JSONUtil.toJsonStr(t));
        valuePOWithBLOBs.setCalledValue(null);
        valuePOWithBLOBs.setReceiveDesc("设备响应");
        valuePOWithBLOBs.setCreateDate(new Date());
        valuePOWithBLOBs.setIsDel(SystemConsts.BooleanEnum.FALSE.value);
        valuePOWithBLOBs.setEnergyType(SystemConsts.EnergyTypeEnum.WATER.value);
        if (areaArchivesPO != null) {
            valuePOWithBLOBs.setArchives(areaArchivesPO.getId().toString());
            valuePOWithBLOBs.setArchivesAddress(areaArchivesPO.getCustomerAddress());
            valuePOWithBLOBs.setArchivesName(areaArchivesPO.getCustomerName());
            valuePOWithBLOBs.setArchivesNum(areaArchivesPO.getCustomerSerialnumber());
            valuePOWithBLOBs.setAreaOrgId(areaArchivesPO.getMeasureAreaOrg());
            valuePOWithBLOBs.setAreaName(areaArchivesPO.getOreaName());

        }
        valuePOWithBLOBs.setValueType(instructionType);
        valuePOWithBLOBs.setModifyDate(new Date());
        valuePOWithBLOBs.setDoCount(0);
        valuePOWithBLOBs.setDoType(0);
        valuePOWithBLOBs.setEquipmentType(SystemConsts.EquipmentTypeEnum.NBIOT.value);

        //imei
        valuePOWithBLOBs.setNbAddress(imei);
        valuePOWithBLOBs.setParseJsonStr(JSONUtil.toJsonStr(t));
        valuePOWithBLOBs.setParseOriginalJsonStr(JSONUtil.toJsonStr(t));
        valuePOWithBLOBs.setPnReceiveTime(DateUtil.formatDateTime(new Date()));
        valuePOWithBLOBs.setReadState(0);
        valuePOWithBLOBs.setNbDeviceId(deviceId);
        valuePOWithBLOBs.setState(SystemConsts.InstructionStateEnum.SUCCESS.value);
        equipInstructionValueService.save(valuePOWithBLOBs);
    }

    @Override
    public void deviceEvent(DeviceEventDTO deviceEventDTO) {
        log.info(JSONUtil.toJsonStr(deviceEventDTO));

        String deviceId = deviceEventDTO.getDeviceId();
        AreaArchivesPO areaArchivesPO = areaArchivesService.getByDeviceId(deviceId);
        saveInstruction(deviceEventDTO, areaArchivesPO, null, deviceId, SystemConsts.InstructionTypeEnum.DEVICE_EVENT.value);
    }

    @Override
    public void deviceOnOffline(DeviceOnOfflineDTO deviceOnOfflineDTO) {
        log.info(JSONUtil.toJsonStr(deviceOnOfflineDTO));
        String deviceId = deviceOnOfflineDTO.getDeviceId();
        AreaArchivesPO areaArchivesPO = areaArchivesService.getByDeviceId(deviceId);
        saveInstruction(deviceOnOfflineDTO, areaArchivesPO, null, deviceId, SystemConsts.InstructionTypeEnum.DEVICE_ON_OFF_LINE.value);
    }
}
